#include <QTimer>
#include <QMouseEvent>
#include "surface_widget.h"
#include <boost/bind.hpp>
#include <boost/lambda/lambda.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <core/util/gl_util.h>

namespace widgets {
  Manipulator *Surface_widget::make_pan_manipulator(const QPoint &pos)
  {
    Manipulator *p_manipulator = Manipulator::make_linear(pos, zoom_ / width(), pan_.x(), -zoom_ / height(), pan_.y());
    connect(p_manipulator, SIGNAL(manipulate_signal(qreal,qreal)), this, SLOT(set_pan(qreal,qreal)));
    return p_manipulator;
  }

  Manipulator *Surface_widget::make_rotate_manipulator(const QPoint &pos)
  {
    const qreal sensitivity = 0.5;
    Manipulator *p_manipulator = Manipulator::make_linear(pos, -sensitivity, rotation_.y(), -sensitivity, rotation_.x());
    connect(p_manipulator, SIGNAL(manipulate_signal(qreal, qreal)), this, SLOT(set_rotation(qreal, qreal)));
    return p_manipulator;
  }

  Manipulator *Surface_widget::make_zoom_manipulator(const QPoint &pos)
  {
    const qreal sensitivity = 1.01;
    Manipulator *p_manipulator = Manipulator::make_exponential(pos, 0.0, 0.0, zoom_, sensitivity);
    connect(p_manipulator, SIGNAL(manipulate_y_signal(qreal)), this, SLOT(set_zoom(qreal)));
    return p_manipulator;
  }

  Manipulator *Surface_widget::make_proportion_manipulator(const QPoint &pos)
  {
    const qreal sensitivity = 1.01;
    Manipulator *p_manipulator = Manipulator::make_exponential(pos, 0.0, 0.0, proportion_, sensitivity);
    connect(p_manipulator, SIGNAL(manipulate_y_signal(qreal)), this, SLOT(set_proportion(qreal)));
    return p_manipulator;
  }

  Manipulator *Surface_widget::make_scale_manipulator(const QPoint &pos)
  {
    const qreal sensitivity = 0.99;
    Manipulator *p_manipulator = Manipulator::make_exponential(pos, 0.0, 0.0, scale_, sensitivity);
    connect(p_manipulator, SIGNAL(manipulate_y_signal(qreal)), this, SLOT(set_scale(qreal)));
    return p_manipulator;
  }

  void Surface_widget::zoom_in()
  {
    set_zoom(zoom_ / 1.5);
  }

  void Surface_widget::zoom_out()
  {
    set_zoom(zoom_ * 1.5);
  }

  Surface_widget::Surface_widget(QWidget *parent)
    : QGLWidget(parent),
    pan_(0, 0),
    rotation_(0, 0),
    zoom_(3.0),
    proportion_(1.0),
    scale_(1.0),
    input_filter_(this)
  {
    using namespace boost;
    input_filter_.reg_manipulator(Qt::LeftButton, Qt::NoModifier, bind(&Surface_widget::make_pan_manipulator, this, _1));
    input_filter_.reg_manipulator(Qt::LeftButton, Qt::ControlModifier, bind(&Surface_widget::make_rotate_manipulator, this, _1));
    input_filter_.reg_manipulator(Qt::LeftButton, Qt::ShiftModifier, bind(&Surface_widget::make_zoom_manipulator, this, _1));
    input_filter_.reg_manipulator(Qt::RightButton, Qt::ControlModifier, bind(&Surface_widget::make_proportion_manipulator, this, _1));
    input_filter_.reg_manipulator(Qt::RightButton, Qt::ShiftModifier, bind(&Surface_widget::make_scale_manipulator, this, _1));

    input_filter_.reg_key_stroke(Qt::Key_Equal, Qt::NoModifier, bind(&Surface_widget::zoom_in, this));
    input_filter_.reg_key_stroke(Qt::Key_Minus, Qt::NoModifier, bind(&Surface_widget::zoom_out, this));
  }

  Surface_widget::~Surface_widget()
  {

  }

  void Surface_widget::set_pan(qreal x_pan, qreal y_pan)
  {
    pan_.setX(x_pan);
    pan_.setY(y_pan);
    updateGL();
  }

  void Surface_widget::set_rotation(qreal z_axis, qreal x_axis)
  {
    x_axis = qMax(qMin(x_axis, 180.0), 0.0);
    rotation_.setX(x_axis);
    rotation_.setY(z_axis);
    updateGL();
  }

  void Surface_widget::set_zoom(qreal zoom)
  {
    zoom_ = zoom;
    updateGL();
  }

  void Surface_widget::set_proportion(qreal proportion)
  {
    proportion_ = proportion;
    updateGL();
  }

  void Surface_widget::set_scale(qreal scale)
  {
    scale_ = scale;
    updateGL();
  }


  void Surface_widget::initializeGL()
  {
    qglClearColor(Qt::white);
    glShadeModel(GL_SMOOTH);
    glEnable(GL_DEPTH_TEST);
    glDisable(GL_CULL_FACE);
  }

  void Surface_widget::paintGL()
  {
    using namespace util::gl;
    //  const float proportion = 1.0f;
    //  const float z_scale = 1.5f;
    static int frame = 0;
    //  const float zoom = 3.0f;
    const float angle = frame;
    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    glOrtho(-zoom_ / 2, zoom_ / 2, -zoom_ / 2, zoom_ / 2, -4, 4);
    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    glTranslated(pan_.x(), pan_.y(), 0.0);
    glRotatef(rotation_.x() + 180, 1.0f, 0.0f, 0.0f);
    glRotatef(rotation_.y(), 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    //  qglColor(Qt::white);
    /*  {
    glLightModeli(GL_LIGHT_MODEL_TWO_SIDE, GL_TRUE);
    GLfloat light_pos[] = { 0.0f, 0.0f, 1.0f, 0.0f };
    glLightfv(GL_LIGHT0, GL_POSITION, light_pos);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);
    glEnable(GL_COLOR_MATERIAL);
    glEnable(GL_NORMALIZE);
    }*/

    //  glRotatef(-45, 1.0f, 0.0f, 0.0f);
    //  glRotatef(-angle, 0.0f, 0.0f, 1.0f);
    //  glColor3f(0.0f, 1.0f, 0.0f);

    static bool init = false;
    static matrix_renderer renderer;
    static boost::numeric::ublas::matrix<unsigned short> m;
    if (!init)
    {
      renderer.fill = true;
      renderer.light = false;
      renderer.cross_lines = true;
      renderer.long_lines = true;
      renderer.palette.resize(65536);
      renderer.line_colors << color::black;
      for (size_t i = 0; i <= 65535; ++i)
        renderer.palette[i] = color(0, GLubyte(i / 65535.0 * 255), 0);
      init = true;
      m = boost::numeric::ublas::identity_matrix<unsigned short>(20, 20);
      for (int i = 0; i < m.size1(); ++i)
        for (int j = 0; j < m.size2(); ++j)
          m(i, j) = 65535 * abs(sin(float(10.0 * i * j / (m.size1()*m.size2()))));
    }
    glScalef(proportion_ >= 1.0f ? 1.0f : proportion_, proportion_ >= 1.0f ? 1.0f / proportion_ : 1.0f, scale_);
    glTranslatef(-0.5f, -0.5f, -0.5f);
    glPushMatrix();
    glScalef(1.0f / (m.size1() - 1), 1.0f / (m.size2() - 1), 1.0f / 65535);
    draw_matrix(m, renderer);
    glPopMatrix();
    /*  {
    axis a;
    a.value_step = 0.1;

    a.vector_begin[0] = 0.0f; a.vector_begin[1] = 0.0f; a.vector_begin[2] = 0.0f;
    a.vector_end[0] = 1.0f; a.vector_end[1] = 0.0f; a.vector_end[2] = 0.0f;
    draw_axis(a, this);
    a.vector_begin[0] = 0.0f; a.vector_begin[1] = 0.0f; a.vector_begin[2] = 0.0f;
    a.vector_end[0] = 0.0f; a.vector_end[1] = 1.0f; a.vector_end[2] = 0.0f;
    draw_axis(a, this);
    a.vector_begin[0] = 0.0f; a.vector_begin[1] = 0.0f; a.vector_begin[2] = 0.0f;
    a.vector_end[0] = 0.0f; a.vector_end[1] = 0.0f; a.vector_end[2] = 1.0f;
    draw_axis(a, this);
    }*/
    ++frame;
  }

  void Surface_widget::resizeGL(int width, int height)
  {
    glViewport(0, 0, width, height);
    updateGL();
  }

  void Surface_widget::mouseMoveEvent(QMouseEvent *p_event)
  {
    //  if (p_manipulator_)
    //    p_manipulator_->manipulate(p_event->pos());
  }

  void Surface_widget::mousePressEvent(QMouseEvent *p_event)
  {
    /*  bool track = true;
    bool shift = Qt::ShiftModifier & p_event->modifiers();
    bool ctrl = Qt::ControlModifier & p_event->modifiers();
    switch (p_event->button())
    {
    case Qt::LeftButton:
    if (ctrl)
    { // pan
    p_manipulator_.reset(Manipulator::make_linear(p_event->pos(), zoom_ / width(), pan_.x(), -zoom_ / height(), pan_.y()));
    connect(p_manipulator_.get(), SIGNAL(manipulate_signal(qreal, qreal)), this, SLOT(set_pan(qreal, qreal)));
    }
    else
    track = false;
    break;
    case Qt::RightButton:
    { // rotate
    const qreal sensitivity = 0.5;
    p_manipulator_.reset(Manipulator::make_linear(p_event->pos(), -sensitivity, rotation_.y(), -sensitivity, rotation_.x()));
    connect(p_manipulator_.get(), SIGNAL(manipulate_signal(qreal, qreal)), this, SLOT(set_rotation(qreal, qreal)));
    }
    break;
    case Qt::MidButton:
    { // scale
    const qreal sensitivity = 1.01;
    p_manipulator_.reset(Manipulator::make_exponential(p_event->pos(), 0.0, 0.0, zoom_, sensitivity));
    connect(p_manipulator_.get(), SIGNAL(manipulate_y_signal(qreal)), this, SLOT(set_zoom(qreal)));
    }
    break;
    default:
    track = false;
    }
    if (track)
    setMouseTracking(true);*/
  }

  void Surface_widget::mouseReleaseEvent(QMouseEvent *p_event)
  {
    /*  if (p_manipulator_)
    {
    p_manipulator_.reset();
    setMouseTracking(false);
    }*/
  }

  void Surface_widget::wheelEvent(QWheelEvent *p_event)
  {
    /*  const qreal sensitivity = 1.001;
    boost::scoped_ptr<Manipulator> p_manipulator(Manipulator::make_exponential(QPoint(0, 0), 0.0, 0.0, zoom_, sensitivity));
    connect(p_manipulator.get(), SIGNAL(manipulate_y_signal(qreal)), this, SLOT(set_zoom(qreal)));
    p_manipulator->manipulate(QPoint(0, -p_event->delta()));*/
  }

} // namespace widgets {